﻿namespace TestApp.Tests
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reactive;
    using System.Reactive.Subjects;
    using Contracts;
    using DataService;
    using Microsoft.Reactive.Testing;
    using Moq;
    using NUnit.Framework;
    using Shouldly;

    [TestFixture]
    internal class DataAggregatorFixture
    {
        private readonly HashSet<DataEntityDto> _curEntities = new HashSet<DataEntityDto>();
        private Mock<IDataProvider> _dataProvider;
        private Mock<IDataStore> _dataStore;
        private TestScheduler _scheduler;
        private ITestableObserver<Unit> _hasNewEntityObserver;
        private ISubject<DataEntityDto> _dataEntityObservable;
        private DataEntityDto _entity0;
        private DataEntityDto _entity1;
        private DataEntityDto _entity2;
        private long _dataVersion;
        private IEnumerable<DataEntityDto> _entities;

        [SetUp]
        public void SetUp()
        {
            _dataVersion = 0;
            _curEntities.Clear();
            _scheduler = new TestScheduler();
            _hasNewEntityObserver = _scheduler.CreateObserver<Unit>();
            _dataStore = new Mock<IDataStore>();
            _dataProvider = new Mock<IDataProvider>();
            _dataEntityObservable = new Subject<DataEntityDto>();
            _dataProvider.Setup(i => i.CreateDataSource()).Returns(_dataEntityObservable);
            
            _entity0 = new DataEntityDto(0);
            _entity0.Parameters[2] = 1d;

            _entity1 = new DataEntityDto(1);
            _entity1.Parameters[2] = -1d;

            _entity2 = new DataEntityDto(2);
            _entity2.Parameters[2] = .3d;
        }

        [Test]
        public void ShouldRaiseHasNewEntityEventWhenSubscribe()
        {
            // Given
            var target = CreateInstance();

            // When
            using (target.CreateHasNewEntitySource().Subscribe(_hasNewEntityObserver))
            {
                // Then
                _hasNewEntityObserver.Messages.Count.ShouldBe(1);
            }
        }

        [Test]
        public void ShouldRaiseHasNewEntityEventWhenReceive()
        {
            // Given
            var target = CreateInstance();

            // When
            using (target.CreateHasNewEntitySource().Subscribe(_hasNewEntityObserver))
            {
                _dataEntityObservable.OnNext(_entity0);
                _dataEntityObservable.OnNext(_entity1);
                
                // Then
                _hasNewEntityObserver.Messages.Count.ShouldBe(3);
            }
        }

        [Test]
        public void ShouldSaveDataEntityWhenReceive()
        {
            // Given
            // ReSharper disable once UnusedVariable
            var target = CreateInstance();

            // When
            _dataEntityObservable.OnNext(_entity0);

            // Then
            _dataStore.Verify(i => i.SaveDataEntity(_entity0));
        }

        [Test]
        public void ShouldGetDataEntities()
        {
            // Given
            var target = CreateInstance();

            // When
            SendEntity(_entity0);
            SendEntity(_entity1);
            SendEntity(_entity2);
            long actualDataVersion;
            IEnumerable<EntityDto> actualEntities;
            var hasData = target.TryGetEntities(1, out actualDataVersion, out actualEntities);

            // Then
            hasData.ShouldBe(true);
            actualDataVersion.ShouldBe(3);
            CheckData(actualEntities, .3d, 1d, -1d, .1d, 1d, -1d, .3d);
        }

        [Test]
        public void ShouldNotTryGetEntitiesWhenDidNotReceive()
        {
            // Given
            var target = CreateInstance();

            // When
            long actualDataVersion;
            IEnumerable<EntityDto> actualEntities;
            var hasData = target.TryGetEntities(1, out actualDataVersion, out actualEntities);

            // Then
            hasData.ShouldBe(false);
        }

        private static void CheckData(IEnumerable<EntityDto> actualEntities, double sum, double max, double min, double avg, params double[] vals)
        {
            var actualEntitiesArr = actualEntities.ToArray();
            actualEntitiesArr.Length.ShouldBe(4 + vals.Length);
            CheckAgg(actualEntitiesArr, AggregateType.Sum, actualEntitiesArr.Length - 4, sum);
            CheckAgg(actualEntitiesArr, AggregateType.Max, actualEntitiesArr.Length - 3, max);
            CheckAgg(actualEntitiesArr, AggregateType.Min, actualEntitiesArr.Length - 2, min);
            CheckAgg(actualEntitiesArr, AggregateType.Avg, actualEntitiesArr.Length - 1, avg);
            for (var index = 0; index < vals.Length; index++)
            {
                actualEntitiesArr[index].Parameters[2].ShouldBe(vals[index]);
            }
        }

        private static void CheckAgg(EntityDto[] actualEntitiesArr, AggregateType aggregateType, int index, double val)
        {
            var aggregateEntityDto = actualEntitiesArr[index] as AggregateEntityDto;
            aggregateEntityDto.ShouldNotBe(null);
            aggregateEntityDto.AggregateType.ShouldBe(aggregateType);
            (Math.Abs(aggregateEntityDto.Parameters[2] - val) < .0000001d).ShouldBe(true);
        }

        private IDataAggregator CreateInstance()
        {
            return new DataAggregator(_dataProvider.Object, _dataStore.Object);
        }

        private void SendEntity(DataEntityDto entity)
        {
            _curEntities.Add(entity);
            _dataVersion++;
            _entities = _curEntities.ToArray();
            _dataStore.Setup(i => i.TryGetDataEntities(It.IsAny<long>(), out _dataVersion, out _entities)).Returns(true);
            _dataEntityObservable.OnNext(entity);
        }
    }
}
